﻿using Blm.PgsqlDriver.Helpers;
using Blm.Utils.Extensions;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using VisionCloud.Core;
using VisionCloud.Core.Model.Enums;
using VisionCloud.Core.Model.Ms;
using VisionCloud.Domain.Common;
using VisionCloud.Domain.DtoModel.Ms;
using VisionCloud.Domain.File.Model;
using VisionCloud.Domain.Repository.Interface.Ms;
using VisionCloud.Domain.Repository.Interface.Sys;
using VisionCloud.Domain.UnitOfWork;
using VisionCloud.Service.Ms;
using VisionCloud.Utility;
using VisionCloud.Utility.Redis;

namespace VisionCloud.Service.Applet
{
    public class UploadFileService
    {
        private readonly string UPLOAD_DIR = $"{ConfigHelper.GetValue<string>("TaskDotCloudData")}/DataFile";
        private readonly string TEMP_DIR = $"{ConfigHelper.GetValue<string>("TaskDotCloudData")}/UploadTemp";
        private readonly string Task_Report_Data = ConfigHelper.GetValue<string>("TaskReportData");
        private static readonly string Minio_Server = ConfigHelper.GetValue<string>("MinioOption:Endpoint");

        private const string redisPrex = "Applet:FileUpload";
        public IDotCloudUploadRepository DotCloudUploadRepository { get; set; }
        public IMsTaskRepository MsTaskRepository { get; set; }

        public IMsProjectRepository MsProjectRepository { get; set; }

        public IUnitOfWork UnitOfWork { get; set; }

        private OperatorInfo tokenModel => OperatorProvider.Get();

        public IMsTaskDotCloudRepository MsTaskDotCloudRepository { get; set; }

        public IMsTaskDotCloudBatchRepository MsTaskDotCloudBatchRepository { get; set; }

        public ICSMSTaskRepository CSMSTaskRepository { get; set; }

        public IMsTaskPointsRepository MsTaskPointsRepository { get; set; }

        public IMsProjectStandardRepository MsProjectStandardRepository { get; set; }

        public ISysDeviceRepository SysDeviceRepository { get; set; }

        public IMsPointCompletedRepository MsPointCompletedRepository { get; set; }

        public MsTaskDotCloudService MsTaskDotCloudService { get; set; }

        public BlueDbContext Db { get; set; }

        private string GetRedisKey(string key) => $"{redisPrex}:{key}";

        private string GetTempSaveDir(Guid taskId, int pointIndex) => $"{TEMP_DIR}/{taskId}/{pointIndex}";

        public async Task<UploadResponse> UploadPart(PartUploadDto upload, Stream DataContent)
        {
            var tempSaveDir = GetTempSaveDir(upload.TaskId, upload.PointIndex);
            if (!Directory.Exists(tempSaveDir)) Directory.CreateDirectory(tempSaveDir);
            var tempSaveFile = $"{tempSaveDir}/{upload.Identifier}-{upload.Index}";
            var result = new UploadResponse { TemFilePath = tempSaveFile };
            var redisKey = GetRedisKey(upload.Identifier);
            if (File.Exists(tempSaveFile) && RedisHelper.Instance.GetIntList(redisKey).Contains(upload.Index))
            {
                return result; //如果分片已经存在，则直接返回
            }

            if (upload.Index == 0)
            {
                //第一片记录为上传中状态
                var projectId = await MsTaskRepository.GetAll().Where(x => x.TaskId == upload.TaskId).Select(x => x.ProjectId).FirstOrDefaultAsync();
                var projectNo = await MsProjectRepository.GetAll().Where(x => x.ProjectId == projectId).Select(x => x.ProjectNo).FirstOrDefaultAsync();
                var arr = projectNo.Split("-");
                var fileNamWithoutExt = Path.GetFileNameWithoutExtension(upload.FileName);
                var fileExt2 = Path.GetExtension(upload.FileName);
                var fileExt1 = Path.GetExtension(fileNamWithoutExt);
                var now = DateTime.Now;
                var query_taskpoint_sql = $"select concat(t.farosn,'#' ,t.build ,'#' ,t.floor)  from taskpointtable t WHERE t.uuid = '{upload.TaskId}' AND t.\"index\"  = {upload.PointIndex};";
                var queryInfo = (await PgSqlHelper.ExecuteScalarAsync(System.Data.CommandType.Text, query_taskpoint_sql)).ToString().Split('#');

                var saveName = $"BZL_{now:yyyyMMddHHmmssfff}_101_{queryInfo[1]}D{queryInfo[2]}F__{upload.TaskId}_{upload.PointIndex:D3}{fileExt1}{fileExt2}";
                await DotCloudUploadRepository.DeleteAsync(x => x.TaskId == upload.TaskId && x.PointIndex == upload.Index);
                var newEntity = new MsDotCloudUpload
                {
                    TaskId = upload.TaskId,
                    FileKey = upload.Identifier,
                    BatchId = upload.BatchId,
                    CreatedBy = tokenModel.Id,
                    FileSize = upload.TotalSize,
                    MeasureType = upload.MeasureType ?? MeasureTypeEnum.实测实量任务,
                    PointIndex = upload.PointIndex,
                    FilePath = $"{UPLOAD_DIR}/{arr[0]}/{arr[1]}/{upload.TaskId}/{saveName}",
                    Begin = now,
                    CreatedTime = now,
                    TotalChunks = upload.TotalChunks,
                    Success = false,
                    Status = UploadStatusEnum.上传中,
                    Farosn = queryInfo[0],
                };
                DotCloudUploadRepository.Add(newEntity, false);
            }
            using var fileStream = new FileStream(tempSaveFile, FileMode.Create);
            await DataContent.CopyToAsync(fileStream);
            RedisHelper.Instance.InsertIntToList(redisKey, upload.Index);
            if (upload.Index == 0) await UnitOfWork.SaveChangesAsync();

            return result;
        }

        private async Task MergeFiles(string[] chunkFilePaths, Stream writeStream)
        {
            foreach (var filePath in chunkFilePaths)
            {
                using (var readStream = new FileStream(filePath, FileMode.Open))
                {
                    await readStream.CopyToAsync(writeStream);
                }

                File.Delete(filePath);
            }
        }

        public async ValueTask<bool> Merge(UploadFileBaseDto dto)
        {
            var uploadEntity = await DotCloudUploadRepository.FirstOrDefaultAsync(x => x.FileKey == dto.Identifier);
            if (uploadEntity != null)
            {
                try
                {
                    var totalChunks = uploadEntity.TotalChunks;
                    var chachedCunks = RedisHelper.Instance.GetIntList(GetRedisKey(dto.Identifier)).Distinct();
                    if (chachedCunks?.Count() != totalChunks) throw new Exception("缺失分片，不能合并");
                    var tempSaveDir = GetTempSaveDir(uploadEntity.TaskId, uploadEntity.PointIndex);
                    var chunkFiles = Directory.GetFiles(tempSaveDir);
                    Array.Sort(chunkFiles, (a, b) => int.Parse(Path.GetFileNameWithoutExtension(a).Split('-')[1]).CompareTo(int.Parse(Path.GetFileNameWithoutExtension(b).Split('-')[1])));
                    var file = uploadEntity.FilePath;
                    var dir = Path.GetDirectoryName(file);
                    if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
                    using FileStream writeStrem = new FileStream(file, FileMode.Create);
                    await MergeFiles(chunkFiles, writeStrem);

                    #region 业务表逻辑

                    var task = await MsTaskRepository.FirstOrDefaultAsync(x => x.TaskId == uploadEntity.TaskId && !x.IsDeleted);
                    var projectStage = await MsProjectStandardRepository.GetAll().Where(x => x.ProjectStandardId == task.ProjectStandardId && x.ProjectGuid == task.ProjectId).Select(x => x.Stage).FirstOrDefaultAsync();
                    var rePath = file.Substring(UPLOAD_DIR.Length + 1);
                    var pathArr = rePath.Split('/');
                    var resultDataPath = $"{Task_Report_Data}/{pathArr[0]}/{pathArr[1]}/{uploadEntity.TaskId}";
                    var csMsTask = await CSMSTaskRepository.GetByIdAsync(uploadEntity.TaskId);
                    var now = DateTime.Now;
                    if (csMsTask == null)
                    {
                        var deviceId = await SysDeviceRepository.GetAll().Where(x => uploadEntity.Farosn.Contains(x.ScannerNumber) && !x.IsDeleted && x.IsActive).Select(x => x.DeviceGuid).FirstOrDefaultAsync();
                        csMsTask = new CSMSTask
                        {
                            DeviceGuid = deviceId,
                            TaskId = uploadEntity.TaskId,
                            ResultDataFilePath = resultDataPath,
                            TaskName = task.TaskName,
                            BuildingName = task.BuildingBlock,
                            FloorName = task.BuildingFloor,
                            TaskExecutionType = task.TaskExecutionType,
                            MeasureType = task.MeasureType,
                            HouseMapId = task.HouseMapId,
                            IsMobileInputManualMeasurement = true,
                            AppUploadDate = now,
                            ProjectId = task.ProjectId,
                            ProjectStage = projectStage,
                            CreatedBy = uploadEntity.CreatedBy.Value,
                            LastUpdatedBy = uploadEntity.CreatedBy.Value,
                            CreatedDate = now,
                            LastUpdatedDate = now,
                            ReportUploadDateTime = now,
                        };
                        CSMSTaskRepository.Add(csMsTask, false);
                    }
                    else if (csMsTask.ResultDataFilePath != resultDataPath || csMsTask.ReportUploadDateTime != now)
                    {
                        csMsTask.ResultDataFilePath = resultDataPath;
                        csMsTask.ReportUploadDateTime = now;
                        CSMSTaskRepository.Update(csMsTask, t => new { t.ResultDataFilePath, t.ReportUploadDateTime });
                    }
                    var taskpoint = await MsTaskPointsRepository.GetAll().Where(x => x.TaskId == uploadEntity.TaskId && x.pintIndex == uploadEntity.PointIndex && !x.IsDeleted).FirstOrDefaultAsync();

                    var MsTaskDotCloudEntity = new MsTaskDotCloud
                    {
                        FileId = Guid.NewGuid(),
                        CreatedBy = uploadEntity.CreatedBy.Value,
                        LastUpdatedBy = uploadEntity.CreatedBy.Value,
                        CreatedDate = now,
                        LastUpdatedDate = now,
                        TaskId = uploadEntity.TaskId,
                        FileName = Path.GetFileName(file),
                        ProjectStage = projectStage,
                        DotCloudFilePath = $"https://{Minio_Server}/minio/task-dotcloud-data/DataFile/{rePath}",
                        ProjectId = task.ProjectId,
                        IsDeleted = false,
                        FileType = 1,
                        IsRead = false,
                        Site = uploadEntity.PointIndex.ToString("D3"),
                        ComputeStatus = 1,
                        TaskTypeName = (await MsTaskRepository.GetAll().Where(t => t.TaskId == uploadEntity.TaskId).Select(x => x.TaskType).FirstOrDefaultAsync()).ToString(),
                    };
                    MsTaskDotCloudRepository.Add(MsTaskDotCloudEntity, false);

                    var pointCompletedEntity = await MsPointCompletedRepository.FirstOrDefaultAsync(x => x.TaskId == uploadEntity.TaskId && x.PointIndex == uploadEntity.PointIndex);
                    if (pointCompletedEntity == null)
                    {
                        MsPointCompletedRepository.Add(new MsPointCompleted
                        {
                            TaskId = uploadEntity.TaskId,
                            PointIndex = uploadEntity.PointIndex,
                            CompletedDate = now,
                        }, false);
                    }
                    else
                    {
                        pointCompletedEntity.CompletedDate = now;
                        pointCompletedEntity.ComputeCompletedDate = null;
                        MsPointCompletedRepository.Update(pointCompletedEntity, false);
                    }

                    #endregion 业务表逻辑

                    uploadEntity.Success = true;
                    uploadEntity.End = now;
                    uploadEntity.Status = UploadStatusEnum.上传成功;
                    DotCloudUploadRepository.Update(uploadEntity, false);

                    await UnitOfWork.SaveChangesAsync();
                    await MsTaskDotCloudService.ApplyCloudMeasure(new List<MeasuringAlgorithmDto>
                    {
                        new MeasuringAlgorithmDto
                        {
                            FileId =MsTaskDotCloudEntity.FileId,
                            TaskId = MsTaskDotCloudEntity.TaskId,
                            PointIndex = MsTaskDotCloudEntity.Site.ToInt32(),
                            ProjectId = MsTaskDotCloudEntity.ProjectId,
                            PcdFile = MsTaskDotCloudEntity.DotCloudFilePath,
                        }
                    });
                    RedisHelper.Instance.DeleteKey(GetRedisKey(dto.Identifier));

                    return true;
                }
                catch (Exception ex)
                {
                    uploadEntity.Status = UploadStatusEnum.上传失败;
                    uploadEntity.Msg = ex.Message;
                    DotCloudUploadRepository.Update(uploadEntity, false);
                    Logger.Error(string.Format("上传合并分片出错,入参为：{0}", dto.ToJsonStr()), ex);
                    return false;
                }
            }
            else
                throw new Exception("无上传记录，合并出错");
        }

        public async Task<VerifyResponse> Verify(UploadFileBaseDto dto)
        {
            var uploadEntity = await DotCloudUploadRepository.FirstOrDefaultAsync(x => x.FileKey == dto.Identifier && x.Success);

            if (uploadEntity != null)
            {
                return new VerifyResponse { NeedUpload = false, Url = uploadEntity.FilePath };
            }
            else
            {
                var chachedCunks = RedisHelper.Instance.GetIntList(GetRedisKey(dto.Identifier)).Distinct().ToArray();
                return new VerifyResponse { NeedUpload = true, UploadedChunks = chachedCunks };
            }
        }
    }
}